package com.mstar.qs.common.json;

import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class JsonBean {

	private static class CycleSet extends ThreadLocal {
		protected Object initialValue() {
			return new SoftReference(new HashSet());
		}

		public Set getSet() {
			Set set = (Set) ((SoftReference) get()).get();
			if (set == null) {
				set = new HashSet();
				set(new SoftReference(set));
			}
			return set;
		}
	}

	private static CycleSet cycleSet = new CycleSet();

	private Map<String, String> keymap = null;
	private Set<String> excluded = null;

	/**
	 * JsonBean.NULL is equivalent to the value that JavaScript calls null,
	 * whilst Java's null is equivalent to the value that JavaScript calls
	 * undefined.
	 */
	private static final class Null {

		/**
		 * There is only intended to be a single instance of the NULL object, so
		 * the clone method returns itself.
		 * 
		 * @return NULL.
		 */
		protected final Object clone() {
			return this;
		}

		/**
		 * A Null object is equal to the null value and to itself.
		 * 
		 * @param object
		 *            An object to test for nullness.
		 * @return true if the object parameter is the JsonBean.NULL object or
		 *         null.
		 */
		public boolean equals(Object object) {
			return object == null || object == this;
		}

		/**
		 * Get the "null" string value.
		 * 
		 * @return The string "null".
		 */
		public String toString() {
			return "null";
		}
	}

	/**
	 * The map where the JsonBean's properties are kept.
	 */
	private final Map map;

	/**
	 * It is sometimes more convenient and less ambiguous to have a
	 * <code>NULL</code> object than to use Java's <code>null</code> value.
	 * <code>JsonBean.NULL.equals(null)</code> returns <code>true</code>.
	 * <code>JsonBean.NULL.toString()</code> returns <code>"null"</code>.
	 */
	public static final Object NULL = new Null();

	/**
	 * Construct a JsonBean from a Map.
	 * 
	 * @param map
	 *            A map object that can be used to initialize the contents of
	 *            the JsonBean.
	 * @throws JsonException
	 */
	public JsonBean(Map map, Map<String, String> keymap, Set<String> excluded) {
		this.map = new HashMap();
		if (map != null) {
			Iterator i = map.entrySet().iterator();
			while (i.hasNext()) {
				Map.Entry e = (Map.Entry) i.next();
				Object value = e.getValue();
				if (value != null) {
					Object o = wrap(value, keymap, excluded);
					if (o != null)
						this.map.put(e.getKey(), o);
				}
			}
		}
	}

	public JsonBean(Object bean, Map<String, String> keymap,
			Set<String> excluded) {
		this.map = new HashMap();
		this.keymap = keymap;
		this.excluded = excluded;
		this.populateMap(bean);

	}

	private void populateMap(Object bean) {

		Class klass = bean.getClass();

		// If klass is a System class then set includeSuperClass to false.

		boolean includeSuperClass = klass.getClassLoader() != null;

		Method[] methods = includeSuperClass ? klass.getMethods() : klass
				.getDeclaredMethods();
		for (int i = 0; i < methods.length; i += 1) {
			try {
				Method method = methods[i];
				if (Modifier.isPublic(method.getModifiers())) {
					String name = method.getName();
//					if("getLanguages")
//					 System.out.println(name);
					String key = "";
					if (name.startsWith("get")) {
						if ("getClass".equals(name)
								|| "getDeclaringClass".equals(name)) {
							key = "";
						} else {
							key = name.substring(3);
						}
					} else if (name.startsWith("is")) {
						key = name.substring(2);
					}
					if (key.length() > 0
							&& Character.isUpperCase(key.charAt(0))
							&& method.getParameterTypes().length == 0) {
						if (key.length() == 1) {
							key = key.toLowerCase();
						} else if (!Character.isUpperCase(key.charAt(1))) {
							key = key.substring(0, 1).toLowerCase()
									+ key.substring(1);
						}
						Object result=null;
						try {
						result = method.invoke(bean, (Object[]) null);
						}catch(Throwable e) {
							e.printStackTrace();
						}
						if (result != null) {
							String rk = keymap(key);
							Object o = wrap(result, keymap, excluded);
							if (o != null) {
								if (excluded != null) {
									if (!excluded.contains(rk))
										this.map.put(rk, o);
								} else {
									this.map.put(rk, o);
								}
							}

						}
					}
				}
			} catch (Exception ignore) {
			}
			// log.info(this.toString());
		}
	}
	
	private String keymap(String key) {
		if (keymap != null && keymap.containsKey(key))
			return keymap.get(key);
		return key;
	}
	
	public static Object wrap(Object object, Map<String, String> keymap,
			Set<String> excluded) {
		try {
			if (object == null) {
				return NULL;
			}
			if (object instanceof JsonBean || object instanceof JsonArray
					|| NULL.equals(object) || object instanceof JsonString
					|| object instanceof Byte || object instanceof Character
					|| object instanceof Short || object instanceof Integer
					|| object instanceof Long || object instanceof Boolean
					|| object instanceof Float || object instanceof Double
					|| object instanceof String) {
				return object;
			}

			if (object instanceof Collection) {
				return new JsonArray((Collection) object, keymap, excluded);
			}
			if (object.getClass().isArray()) {
				return new JsonArray(object, keymap, excluded);
			}
			if (object instanceof Map) {
				return new JsonBean((Map) object, keymap, excluded);
			}
			Package objectPackage = object.getClass().getPackage();
			String objectPackageName = objectPackage != null ? objectPackage
					.getName() : "";
			if (objectPackageName.startsWith("java.")
					|| objectPackageName.startsWith("javax.")
					|| object.getClass().getClassLoader() == null) {
				return object.toString();
			}
			if (!addInstance(object))
				return null;
			return new JsonBean(object, keymap, excluded);
		} catch (Exception exception) {
			return null;
		}
	}
	

	/**
	 * Produce a string in double quotes with backslash sequences in all the
	 * right places. A backslash will be inserted within </, producing <\/,
	 * allowing JSON text to be delivered in HTML. In JSON text, a string cannot
	 * contain a control character or an unescaped quote or backslash.
	 * 
	 * @param string
	 *            A String
	 * @return A String correctly formatted for insertion in a JSON text.
	 */
	public static String quote(String string) {
		if (string == null || string.length() == 0) {
			return "\"\"";
		}

		char b;
		char c = 0;
		String hhhh;
		int i;
		int len = string.length();
		StringBuffer sb = new StringBuffer(len + 4);

		sb.append('"');
		for (i = 0; i < len; i += 1) {
			b = c;
			c = string.charAt(i);
			switch (c) {
			case '\\':
			case '"':
				sb.append('\\');
				sb.append(c);
				break;
			case '/':
				if (b == '<') {
					sb.append('\\');
				}
				sb.append(c);
				break;
			case '\b':
				sb.append("\\b");
				break;
			case '\t':
				sb.append("\\t");
				break;
			case '\n':
				sb.append("\\n");
				break;
			case '\f':
				sb.append("\\f");
				break;
			case '\r':
				sb.append("\\r");
				break;
			default:
				if (c < ' ' || (c >= '\u0080' && c < '\u00a0')
						|| (c >= '\u2000' && c < '\u2100')) {
					hhhh = "000" + Integer.toHexString(c);
					sb.append("\\u" + hhhh.substring(hhhh.length() - 4));
				} else {
					sb.append(c);
				}
			}
		}
		sb.append('"');
		return sb.toString();
	}
	
	private static Set getCycleSet() {
		return cycleSet.getSet();
	}
	
	protected static boolean addInstance(Object instance) {
//		if(getCycleSet().contains(instance)) {
//			Iterator it=getCycleSet().iterator();
//			while(it.hasNext()) {
//				Object o=it.next();
//				if(o.hashCode()==instance.hashCode() && o.equals(instance)) {
//					System.out.println("");
//				}
//			}
//			System.out.println("new: ");
//			System.out.println("");
//		}
		boolean ad=getCycleSet().add(instance);
		return ad;
	}
	
	/**
	 * Make a JSON text of this JsonBean. For compactness, no whitespace is
	 * added. If this would not result in a syntactically correct JSON text,
	 * then null will be returned instead.
	 * <p>
	 * Warning: This method assumes that the data structure is acyclical.
	 * 
	 * @return a printable, displayable, portable, transmittable representation
	 *         of the object, beginning with <code>{</code>&nbsp;<small>(left
	 *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
	 *         brace)</small>.
	 */
	public String toString() {
		try {
			Iterator keys = this.keys();
			StringBuffer sb = new StringBuffer("{");

			while (keys.hasNext()) {
				if (sb.length() > 1) {
					sb.append(',');
				}
				Object o = keys.next();
				sb.append(quote(o.toString()));
				sb.append(':');
				sb.append(valueToString(this.map.get(o), keymap, excluded));
			}
			sb.append('}');
			return sb.toString();
		} catch (Exception e) {
			return null;
		}
	}
	
	/**
	 * Make a JSON text of an Object value. If the object has an
	 * value.toJSONString() method, then that method will be used to produce the
	 * JSON text. The method is required to produce a strictly conforming text.
	 * If the object does not contain a toJSONString method (which is the most
	 * common case), then a text will be produced by other means. If the value
	 * is an array or Collection, then a JsonArray will be made from it and its
	 * toJSONString method will be called. If the value is a MAP, then a
	 * JsonBean will be made from it and its toJSONString method will be
	 * called. Otherwise, the value's toString method will be called, and the
	 * result will be quoted.
	 * 
	 * <p>
	 * Warning: This method assumes that the data structure is acyclical.
	 * 
	 * @param value
	 *            The value to be serialized.
	 * @return a printable, displayable, transmittable representation of the
	 *         object, beginning with <code>{</code>&nbsp;<small>(left
	 *         brace)</small> and ending with <code>}</code>&nbsp;<small>(right
	 *         brace)</small>.
	 * @throws JsonException
	 *             If the value is or contains an invalid number.
	 */
	public static String valueToString(Object value, Map<String, String> keymap,Set<String> excluded) throws JsonException {
		if (value == null || value.equals(null)) {
			return "null";
		}
		if (value instanceof JsonString) {
			Object object;
			try {
				object = ((JsonString) value).toJSONString();
			} catch (Exception e) {
				throw new JsonException(e);
			}
			if (object instanceof String) {
				return (String) object;
			}
			throw new JsonException("Bad value from toJSONString: " + object);
		}
		if (value instanceof Number) {
			return numberToString((Number) value);
		}
		if (value instanceof Boolean || value instanceof JsonBean
				|| value instanceof JsonArray) {
			return value.toString();
		}
		if (value instanceof Map) {
			return new JsonBean((Map) value, keymap, excluded).toString();
		}
		if (value instanceof Collection) {
			return new JsonArray((Collection) value, keymap, excluded).toString();
		}
		if (value.getClass().isArray()) {
			return new JsonArray(value, keymap, excluded).toString();
		}
		return quote(value.toString());
	}
	
	/**
	 * Throw an exception if the object is a NaN or infinite number.
	 * 
	 * @param o
	 *            The object to test.
	 * @throws JsonException
	 *             If o is a non-finite number.
	 */
	public static void testValidity(Object o) throws JsonException {
		if (o != null) {
			if (o instanceof Double) {
				if (((Double) o).isInfinite() || ((Double) o).isNaN()) {
					throw new JsonException(
							"JSON does not allow non-finite numbers.");
				}
			} else if (o instanceof Float) {
				if (((Float) o).isInfinite() || ((Float) o).isNaN()) {
					throw new JsonException(
							"JSON does not allow non-finite numbers.");
				}
			}
		}
	}
	
	/**
	 * Produce a string from a Number.
	 * 
	 * @param number
	 *            A Number
	 * @return A String.
	 * @throws JsonException
	 *             If n is a non-finite number.
	 */
	public static String numberToString(Number number) throws JsonException {
		if (number == null) {
			throw new JsonException("Null pointer");
		}
		testValidity(number);

		// Shave off trailing zeros and decimal point, if possible.

		String string = number.toString();
		if (string.indexOf('.') > 0 && string.indexOf('e') < 0
				&& string.indexOf('E') < 0) {
			while (string.endsWith("0")) {
				string = string.substring(0, string.length() - 1);
			}
			if (string.endsWith(".")) {
				string = string.substring(0, string.length() - 1);
			}
		}
		return string;
	}
	
	/**
	 * Get an enumeration of the keys of the JsonBean.
	 * 
	 * @return An iterator of the keys.
	 */
	public Iterator keys() {
		return this.map.keySet().iterator();
	}
	
	
	
}

